5.07. Переменные и типы данных
Переменные и типы данных
PHP — скриптовый язык общего назначения, изначально созданный для веб-разработки, но впоследствии значительно расширивший свои возможности. Одной из ключевых особенностей PHP является его динамическая типизация, что означает: тип данных переменной определяется интерпретатором — в процессе выполнения программы, на основании присваиваемого значения. Это упрощает написание кода для быстрого прототипирования и веб-сценариев, но требует от разработчика повышенного внимания к семантике операций, особенно при сравнениях, арифметике и передаче данных между слоями приложения.
В настоящей главе последовательно рассматриваются:
- природа переменных в PHP (лексика, область видимости, особенности именования);
- система типов: скалярные, составные и специальные типы;
- механизм неявного и явного преобразования типов;
- инструменты проверки и работы с типами;
- последствия динамической типизации для надёжности и читаемости кода;
- практики, способствующие снижению рисков, связанных с нестрогой типизацией.
1. Переменные: синтаксис и семантика
В PHP переменная — это именованный контейнер для временного хранения значения в памяти. Все переменные в PHP начинаются со знака доллара ($), за которым следует идентификатор. Идентификатор должен соответствовать следующим правилам:
- первый символ после
$— буква латинского алфавита (a–z,A–Z) или символ подчёркивания (_); - последующие символы могут включать буквы, цифры (
0–9) и подчёркивания; - регистр имеет значение:
$name,$Nameи$NAME— три разные переменные.
Примеры корректных имён:
$userName
$_cacheKey
$totalAmount2024
Примеры некорректных имён:
$1user // начинается с цифры — синтаксическая ошибка
$user-name // дефис недопустим — интерпретируется как вычитание
$user name // пробел недопустим — синтаксическая ошибка
В отличие от некоторых других языков (например, C# или Java), в PHP отсутствует обязательное объявление переменной перед использованием. Переменная считается созданной в момент первого присваивания значения. Если переменная используется до присваивания, PHP генерирует предупреждение уровня E_NOTICE, а само выражение ведёт себя так, будто переменная содержит значение null. Это поведение, с одной стороны, упрощает написание небольших сценариев, с другой — повышает риск опечаток и трудноуловимых логических ошибок. Поэтому настоятельно рекомендуется включать режим отладки с полным набором отчётов об ошибках (error_reporting = E_ALL) на всех этапах разработки, включая тестирование.
Область видимости переменных в PHP определяется контекстом: глобальная, локальная (внутри функции), статическая (сохраняющая значение между вызовами функции), или суперглобальная (предопределённые массивы, такие как $_GET, $_POST, $_SERVER). Переменные, объявленные вне функций и классов, существуют в глобальной области видимости. Внутри функций по умолчанию доступны только локальные переменные; для доступа к глобальной переменной необходимо явно использовать ключевое слово global или обращаться через суперглобальный массив $GLOBALS. Такое поведение способствует инкапсуляции, но требует осознанного управления состоянием.
В PHP переменные реализованы как zval (Zend value) — внутренняя структура данных Zend Engine, содержащая само значение, его тип, счётчик ссылок и флаги. Это позволяет эффективно реализовать семантику копирования при записи (copy-on-write): при присваивании одной переменной другой на уровне PHP создаётся новая ссылка на тот же zval; копирование происходит только в момент модификации. Таким образом, PHP балансирует между удобством высокоуровневых операций и производительностью низкоуровневого выполнения.
2. Система типов в PHP
PHP поддерживает восемь типов данных, которые условно делятся на три категории:
2.1. Скалярные типы (scalar types)
К ним относятся значения, представляющие единичные, неделимые сущности:
-
boolean — логический тип, принимающий ровно два значения:
trueиfalse. В контекстах, ожидающих булево значение (например, в условных операторахif,while), PHP применяет правила приведения к булеву типу (boolean conversion). Следующие значения считаются ложными (false):
false,0(целое),0.0(вещественное), пустая строка""или строка"0", пустой массив[],null, а также объекты некоторых расширений (например,SimpleXMLс пустым документом).
Все остальные значения интерпретируются какtrue. -
integer — целое число со знаком. Диапазон зависит от разрядности системы: на 32-битных платформах — от −2 147 483 648 до 2 147 483 647; на 64-битных — от −9 223 372 036 854 775 808 до 9 223 372 036 854 775 807. PHP поддерживает запись целых чисел в десятичной, шестнадцатеричной (
0x...), восьмеричной (0...) и двоичной (0b...) системах счисления. Переполнение целого числа автоматически преобразует его в типfloat, что может привести к потере точности. -
float (также известен как double или real) — число с плавающей запятой. Реализован в соответствии со стандартом IEEE 754 двойной точности (64 бита). Следует помнить, что числа с плавающей запятой не могут точно представлять все десятичные дроби (например,
0.1 + 0.2 !== 0.3), что является фундаментальным ограничением двоичной арифметики. Для финансовых расчётов рекомендуется использовать расширениеbcmathили хранить денежные суммы в минимальных единицах (например, копейках) как целые числа. -
string — последовательность байтов, интерпретируемых как текст. Строка может быть в UTF-8, Windows-1251, KOI8-R и т.д. Важно, что встроенные строковые функции (например,
strlen(),substr()) работают с байтами, а не с символами Unicode. Для корректной работы с многобайтовыми кодировками (в первую очередь UTF-8) следует использовать функции из расширенияmbstring(mb_strlen(),mb_substr()и др.). Строки могут быть объявлены в одинарных ('...'), двойных ("...") кавычках или с использованием синтаксиса heredoc/nowdoc. В двойных кавычках и heredoc поддерживаются интерполяция переменных и escape-последовательности (например,"\n"), в одинарных — только escape-последовательности\'и\\.
2.2. Составные типы (compound types)
-
array — упорядоченная коллекция пар ключ–значение. Ключом может быть целое число (
integer) или строка (string); значением — любой тип, включая другой массив (вложенность не ограничена). Массивы в PHP объединяют функциональность списков, словарей, хэш-таблиц и множеств. Существует два основных вида: индексированные (ключи — последовательные целые числа, начиная с 0) и ассоциативные (ключи — произвольные строки или числа). С версии PHP 7.4 появилась поддержка типизированных массивов в объявлениях свойств классов (array<string, int>), а с PHP 8.0 — union-типов иmixed, что улучшает статическую проверку. -
object — экземпляр класса. Объекты передаются по ссылке (начиная с PHP 5; в PHP 4 передавались по значению). Объект может содержать свойства и методы, наследоваться от других классов, реализовывать интерфейсы. Даже если все свойства объекта удалены или имеют значение
null, сам объект не равенnull:new stdClass() !== null.
2.3. Специальные типы
-
null — единственный возможный тип значения
null. Обозначает отсутствие значения. Переменная имеет типnull, если:
— ей явно присвоеноnull;
— она объявлена, но ей ещё не присвоено никакое значение;
— она была уничтожена с помощьюunset().
PHP не различает необъявленную и объявленную, но имеющую значениеnullпеременную при чтении — в обоих случаях возникаетE_NOTICE, а результат выражения —null. Однако функцияisset()вернётfalseи в первом, и во втором случае, тогда какarray_key_exists()или оператор??(null coalescing) позволяют различать «отсутствие» и «явное null» в массивах и свойствах объектов. -
resource (устаревший) — специальный тип, представляющий ссылку на внешний ресурс: дескриптор файла, соединение с БД, изображение GD и т.п. Начиная с PHP 8.0 многие ресурсы были заменены на объекты (например,
mysqliвместо ресурсаmysql), и типresourceпостепенно выводится из употребления. Тем не менее, в унаследованном коде он встречается часто. Ресурсы автоматически освобождаются при уничтожении переменной, но рекомендуется закрывать их явно (например,fclose(),mysqli_close()), особенно в долгоживущих скриптах.
Дополнительно, начиная с PHP 8.0, введён псевдотип mixed, обозначающий «любой тип», и с PHP 8.1 — never (для функций, которые никогда не возвращают управление). Эти типы используются в строгих объявлениях типов и аналитических инструментах, но не влияют на runtime-поведение.
3. Преобразование типов: неявное и явное
Одна из самых дискуссионных особенностей PHP — его активное использование неявного (автоматического) приведения типов. Интерпретатор стремится избежать фатальных ошибок при выполнении операций с несовместимыми типами, поэтому вместо прерывания выполнения он пытается преобразовать операнды к «подходящему» типу в соответствии с внутренними правилами. Эти правила последовательно закреплены в документации и реализованы в Zend Engine; однако их сложность и контекстная зависимость часто становятся источником неожиданного поведения.
3.1. Контексты, вызывающие неявное приведение
Неявное преобразование происходит в следующих ситуациях:
-
Арифметические операции (
+,-,*,/,%,**).
Все операнды приводятся к числу — либоinteger, либоfloat, в зависимости от содержимого. Если строка начинается с цифр (возможно, с ведущих пробелов), она интерпретируется как число до первого недопустимого символа:"123abc"→123,"12.3e2xyz"→1230.0. Строка, не начинающаяся с цифры (например,"abc123"), превращается в0. Пустая строка""и строка"0"также дают0, но с разными флагами внутреннего представления — это может повлиять на последующее сравнение. -
Логические операции и условия (
&&,||,!,if,while,?:).
Все значения приводятся кbooleanпо правилам, описанным ранее. Особенно важно помнить, что строка"0"— единственная непустая строка, которая считается ложной. -
Строковый контекст (конкатенация
.,echo,print, интерполяция в двойных кавычках).
Значения приводятся к строке. Дляnullрезультат — пустая строка""; дляboolean—"1"(true) или""(false); дляinteger/float— десятичное представление; дляarrayилиobjectбез метода__toString()— ошибка уровняE_RECOVERABLE_ERROR(начиная с PHP 8.0 —TypeError). -
Сравнение с оператором равенства (
==).
PHP приводит оба операнда к общему типу, руководствуясь таблицей loose comparison. Например,"0" == false→true,0 == "abc"→true,"123" == 123→true, но"123a" == 123→true, а"a123" == 123→false. Такое поведение делает==крайне ненадёжным для валидации входных данных.
3.2. Явное приведение (кастинг)
Разработчик может управлять преобразованием типов вручную, используя один из трёх механизмов:
-
Оператор кастинга в скобках (
(type)).
Это наиболее распространённый и производительный способ. Поддерживаемые формы:(int),(integer)(bool),(boolean)(float),(double),(real)(string)(array)(object)(unset)— эквивалентен присваиваниюnull(устаревшее, не рекомендуется)
Примеры:
$str = " 42 apples";
$num = (int)$str; // 42 — отбрасывается всё после цифр
$bool = (bool)"0"; // false — строка "0" → false
$arr = (array)$num; // [0 => 42]
$obj = (object)['x' => 1]; // объект stdClass со свойством x = 1Кастинг — это выражение. Он создаёт новое значение заданного типа.
-
Функция
settype($var, $type).
Изменяет непосредственно переменную, переданную по ссылке. Возвращаетtrueпри успехе,false— при недопустимом типе. Поддерживает те же строковые имена типов, что и оператор кастинга:'integer','double','string','array','object','boolean','null'.$value = "123";
settype($value, 'integer'); // $value теперь 123 (int)Преимущество
settype()— возможность динамического указания целевого типа (например, из конфигурации). Недостаток — побочный эффект изменения переменной и чуть более высокие накладные расходы. -
Специализированные функции преобразования.
Некоторые преобразования требуют дополнительной логики и реализованы в виде отдельных функций:intval(),floatval(),strval()— аналоги(int),(float),(string), но принимают второй параметр (основание системы счисления дляintval()).boolval()(PHP 5.5+) — явное приведение кboolean.json_encode()/json_decode()— сериализация/десериализация с контролем типов.filter_var()— валидация и санитизация с приведением (например,FILTER_VALIDATE_INT).
3.3. Таблица приоритетов при неявном приведении
При операциях с разнотипными операндами PHP выбирает целевой тип по следующим приоритетам (от высшего к низшему):
| Контекст | Целевой тип | Пояснение |
|---|---|---|
| Арифметика | float → integer | float имеет приоритет над integer. Если хотя бы один операнд float → результат float. |
Сравнение (==) | Зависит от пары типов | Используется внутренняя таблица loose comparison (см. ниже). |
| Логический контекст | boolean | Всё приводится к true/false. |
| Строковый контекст | string | Вызывается внутренний механизм преобразования в строку (например, zend_string_from_long). |
Для сравнения через == действует следующее упрощённое правило (полная таблица — в официальной документации PHP):
- Если один из операндов —
boolean, оба приводятся кboolean. - Иначе, если один из операндов —
object, применяется специальная логика (часто — попытка cast кstringилиint). - Иначе, если оба операнда — строки, сравниваются как строки (лексикографически, побайтово).
- Иначе — оба приводятся к
floatи сравниваются численно.
Отсюда, например, следует:
0 == "0e12345"→true(оба становятсяfloat:0.0 == 0.0);"1" == "01"→false(сравнение как строк:"1"≠"01");true == "php"→true("php"→true,true == true).
4. Проверка типов: инструменты и практики
PHP предоставляет два семейства функций для работы с типами: определение текущего типа и проверка на соответствие типу.
4.1. gettype($var)
Функция возвращает строку с именем типа переменной:
"boolean", "integer", "double" (обратите внимание: не "float"), "string", "array", "object", "resource", "NULL".
Это — диагностический инструмент, полезный при отладке. Однако не следует использовать gettype() в условиях ветвления, поскольку:
- строковое сравнение медленнее прямой проверки;
- имена типов (например,
"double") могут ввести в заблуждение; - логика, зависящая от конкретного типа, часто нарушает принципы полиморфизма.
Пример (не рекомендуется в production-коде):
if (gettype($input) === 'string') {
// обработка строки
}
4.2. Семейство функций is_*()
Это — основной механизм проверки типов в runtime. Все функции возвращают true или false. Наиболее важные:
| Функция | Проверяет | Особенности |
|---|---|---|
is_bool($v) | boolean | — |
is_int($v), is_integer($v), is_long($v) | integer | Три синонима. |
is_float($v), is_double($v), is_real($v) | float | Три синонима. |
is_string($v) | string | — |
is_array($v) | array | — |
is_object($v) | object | — |
is_null($v) | null | Эквивалент ($v === null) |
is_resource($v) | resource | Устаревает, но актуален для legacy-кода |
is_numeric($v) | число или строка, содержащая число | Возвращает true для "123", "12.3", "1e5", но false для "123abc" |
is_scalar($v) | скалярный тип (bool, int, float, string) | — |
is_callable($v) | может ли значение быть вызвано как функция | Проверяет Closure, имя функции, [$obj, 'method'] и др. |
Обратите внимание на различие между is_numeric() и (float)-кастингом:
is_numeric("123abc") → false, а (float)"123abc" → 123.0.
4.3. Оператор instanceof
Для объектов ключевой инструмент — оператор instanceof. Он проверяет, является ли объект экземпляром заданного класса, его наследником или реализует указанный интерфейс.
if ($logger instanceof Psr\Log\LoggerInterface) {
$logger->info('Event occurred');
}
Это предпочтительный способ проверки типов объектов — он устойчив к рефакторингу и поддерживает полиморфизм.
5. Сравнение: == против ===
PHP поддерживает два оператора равенства:
==(loose equality) — сравнивает значения после неявного приведения типов.===(strict equality) — сравнивает и значение, и тип; возвращаетtrueтолько если оба операнда идентичны по значению и типу.
Примеры:
"0" == 0; // true — loose: string → int
"0" === 0; // false — strict: string ≠ integer
null == false; // true — loose: null → false
null === false; // false — strict: null ≠ boolean
"123" == 123; // true
"123" === 123; // false
true == "1"; // true
true === "1"; // false
Рекомендация: всегда используйте === и !==, за исключением случаев, когда loose-сравнение намеренно и осознанно применяется (например, при обработке пользовательского ввода, где "0" и 0 семантически равнозначны). Особенно критично это при:
- валидации форм;
- работе с возвращаемыми значениями функций (некоторые функции возвращают
falseпри ошибке и0как корректный результат); - сравнении с
null(используйте=== null, а не== null).
Начиная с PHP 8.0, введён оператор nullsafe (?->), а с PHP 7.0 — оператор null coalescing (??), которые позволяют избежать явных проверок === null в цепочках вызовов и извлечении значений:
$username = $_GET['user'] ?? 'guest';
$length = $user?->getName()?->length ?? 0;
6. Рекомендации по работе с типами в PHP
Несмотря на динамическую природу языка, современный PHP (начиная с версии 7.0) предоставляет всё больше инструментов для повышения типовой дисциплины:
-
Включайте строгий режим типов через директиву
declare(strict_types=1);в начале файла.
Это заставляет интерпретатор требовать точного совпадения типов в объявлениях параметров и возвращаемых значениях функций (но не влияет на внутренние операции, например,+или==).
Пример:declare(strict_types=1);
function add(int $a, int $b): int {
return $a + $b;
}
add(2, "3"); // Fatal error: Argument 2 passed to add() must be of type int, string given -
Используйте типизированные объявления в сигнатурах:
- параметры (
function f(string $name)); - возвращаемые значения (
function id(): int); - свойства классов (PHP 7.4+ —
private string $name;); - типизированные свойства в анонимных классах и трейтах.
- параметры (
-
Предпочитайте скалярные типы (
string,int,bool,float) union-типам иmixed, когда это возможно. Union-типы (string|int) допустимы, но их избыток усложняет анализ. -
Используйте статические анализаторы — PHPStan, Psalm. Они способны обнаружить несоответствия типов на этапе разработки, даже без
strict_types=1. Интеграция в CI/CD позволяет блокировать слияние кода с типовыми ошибками. -
Избегайте «магических» приведений в критических местах:
- не используйте
==для сравнения с0,"",false,null; - не полагайтесь на неявное приведение при обработке пользовательского ввода — сначала валидируйте (
filter_var,is_numeric), затем кастите; - при работе с JSON — указывайте
JSON_THROW_ON_ERRORи проверяйте типы послеjson_decode().
- не используйте
-
Документируйте ожидаемые типы с помощью PHPDoc, даже если используется строгая типизация:
/**
* @param array<string, mixed> $config
* @return array<string, string>
*/
function normalizeConfig(array $config): array { /* ... */ }